/*
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "runtime/include/thread_scopes.h"
#include "runtime/include/class.h"
#include "runtime/include/class_linker.h"
#include "runtime/include/class_linker-inl.h"
#include "runtime/include/method.h"
#include "runtime/include/runtime.h"
#include "verification/jobs/job.h"

namespace ark::verifier {

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define LOG_INST()                                                                                              \
    LOG(DEBUG, VERIFIER) << "JOBFILL: " << std::hex << std::setw(sizeof(inst.GetOffset())) << std::setfill('0') \
                         << inst.GetOffset() << std::dec << ": " << inst

// NOLINTNEXTLINE(readability-function-size)
inline bool Job::ResolveIdentifiers() {
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvoid-ptr-dereference"
#pragma clang diagnostic ignored "-Wgnu-label-as-value"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif

    LOG(DEBUG, VERIFIER) << "JOBFILL: Filling Job cache for method '" << method_->GetFullName() << "'";

constexpr std::size_t DISPATCH_TABLE_HANDLER_NAMES_SIZE = <%= Panda::dispatch_table.handler_names.size %>;
std::array<const void*, DISPATCH_TABLE_HANDLER_NAMES_SIZE> dispatchTable{
% Panda::dispatch_table.handler_names.each do |name|
        &&HANDLE_<%= name %>,
% end
    };

    ErrorHandler errorHandler;
    const uint8_t *start = method_->GetInstructions();
    size_t codeSize = method_->GetCodeSize();
    if (codeSize == 0) {
        LOG(ERROR, VERIFIER) << "Zero code size is not valid. Function must contain return instruction";
        return false;
    }
    const uint8_t *end = &start[codeSize];  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)

    BytecodeInstructionSafe inst{start, start, end};
    uint8_t secondaryOpcode;

    if (!inst.IsPrimaryOpcodeValid()) {
        LOG(ERROR, VERIFIER) << "Incorrect opcode";
        return false;
    }
    goto* dispatchTable[inst.GetPrimaryOpcode()];

% dt_non_prefixed_min_minus = (Panda::dispatch_table.invalid_non_prefixed_interval.min - 1).to_s
% dt_non_prefixed_max_plus = (Panda::dispatch_table.invalid_non_prefixed_interval.max + 1).to_s
% dt_prefixed_min_plus = (Panda::dispatch_table.invalid_prefixes_interval.min + 1).to_s
% dt_prefixed_max_plus = (Panda::dispatch_table.invalid_prefixes_interval.max + 1).to_s
%
% dispatch_table_hash = Hash.new()
% Panda::instructions.each do |i|
%   combination_flags = ""
%   mnemonic = i.mnemonic.split('.').map { |p| p == '64' ? 'Wide' : p.capitalize }.join
%   value_dispatch = "HANDLE_" + i.handler_name
%   if i.properties.any? {  |prop| ['method_id', 'static_method_id', 'field_id', 'static_field_id', 'type_id', 'literalarray_id'].include?(prop)}
%                           combination_flags += "Prop_"
%   end
%   if (['method_id', 'static_method_id', 'field_id', 'static_field_id', 'type_id', 'string_id', 'literalarray_id'] & i.properties).size > 1
%       cache_api = "cache_api"
%       combination_flags += "CacheApi_"
%   else
%       cache_api = "cache.FastAPI()"
%       combination_flags += "CacheFastApi_"
%   end
%   if i.properties.include?('literalarray_id')
%       combination_flags += "LiteralliId_"
%   end
%   if i.properties.include?('method_id') || i.properties.include?('static_method_id')
%       combination_flags += "MethodId_"
%   end
%   if i.properties.include?('field_id')
%       combination_flags += "FieldId_"
%   end
%   if i.properties.include?('static_field_id')
%       combination_flags += "StaticFieldId_"
%   end
%   if i.properties.include?('type_id')
%       if i.verification.include?('type_id_class')
%           combination_flags += "ClassId_"
%       else
%           combination_flags += "TypeId_"
%       end
%   end
%   if i.properties.include?('string_id')
%       combination_flags += "StringId_"
%   end
%   combination_flags += "GetNext_"
%   flag = dispatch_table_hash.include?(combination_flags)
%   if flag == false
%       dispatch_table_hash[combination_flags] = []
%   end
%   dispatch_table_hash[combination_flags].push(value_dispatch)
% end
% body_gen_parts = Hash.new()
% body_gen_parts = {
%    "Prop_" => %(
%        LOG_INST();
%        [[maybe_unused]] auto id = inst.GetId();
%    ),
%    "CacheApi_" => %(),
%    "CacheFastApi_" => %(),
%    "LiteralliId_" => %({
%        const auto& pf = *method_->GetPandaFile();
%        panda_file::LiteralTag tag;
%        panda_file::LiteralDataAccessor::LiteralValue value;
%        if (!Runtime::GetLiteralTagAndValue(pf, pf.GetLiteralArrays()[id.AsIndex()], &tag, &value)) {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot get literal tag with id=" << std::hex << id << " for offset 0x" << std::hex << inst.GetOffset();
%        } else {
%            Class const *cachedClass = nullptr;
%            auto resolveAndLink = [&](const char *descr) {
%                ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%                cachedClass = classLinker_->GetClass(utf::CStringAsMutf8(descr), false, classLinkerCtx_, &errorHandler);
%            };
%            // descriptors for primitive types get from libpandafile/templates/type
%            switch (tag) {
%                case panda_file::LiteralTag::ARRAY_U1:
%                    resolveAndLink("[Z");
%                    break;
%                case panda_file::LiteralTag::ARRAY_U8:
%                    resolveAndLink("[H");
%                    break;
%                case panda_file::LiteralTag::ARRAY_I8:
%                    resolveAndLink("[B");
%                    break;
%                case panda_file::LiteralTag::ARRAY_I16:
%                    resolveAndLink("[S");
%                    break;
%                case panda_file::LiteralTag::ARRAY_U16:
%                    resolveAndLink("[C");
%                    break;
%                case panda_file::LiteralTag::ARRAY_U32:
%                    resolveAndLink("[U");
%                    break;
%                case panda_file::LiteralTag::ARRAY_I32:
%                    resolveAndLink("[I");
%                    break;
%                case panda_file::LiteralTag::ARRAY_U64:
%                    resolveAndLink("[Q");
%                    break;
%                case panda_file::LiteralTag::ARRAY_I64:
%                    resolveAndLink("[J");
%                    break;
%                case panda_file::LiteralTag::ARRAY_F32:
%                    resolveAndLink("[F");
%                    break;
%                case panda_file::LiteralTag::ARRAY_F64:
%                    resolveAndLink("[D");
%                    break;
%                case panda_file::LiteralTag::ARRAY_STRING: {
%                    ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%                    cachedClass = classLinker_->GetClass(langContext_.GetStringArrayClassDescriptor(), false, classLinkerCtx_, &errorHandler);
%                    break;
%                }
%                default:
%                    break;
%            }
%            if (cachedClass != nullptr) {
%                auto type = Type {cachedClass};
%                AddType(inst.GetOffset(), &type);
%            } else {
%                LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot find class for literal with id=" << std::hex << id << " for offset 0x" << std::hex << inst.GetOffset();
%            }
%        }
%    }),
%    "MethodId_" => %({
%        auto methodId = method_->GetClass()->ResolveMethodIndex(id.AsIndex());
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        const auto *pf = method_->GetPandaFile();
%        Method *calledMethod = pf->GetPandaCache()->GetMethodFromCache(methodId);
%        // Special case for array constructors, which are fake methods that other
%        // Panda code treats in a special way
%        bool isArrayConstructorCall = false;
%        if (calledMethod == nullptr) {
%            panda_file::MethodDataAccessor const mda(*pf, methodId);
%            Class *cls = classLinker_->GetClass(*method_, mda.GetClassId(), &errorHandler);
%
%            auto opcode = inst.GetOpcode();
%            if (UNLIKELY((opcode == BytecodeInstructionSafe::Opcode::INITOBJ_SHORT_V4_V4_ID16 ||
%                          opcode == BytecodeInstructionSafe::Opcode::INITOBJ_V4_V4_V4_V4_ID16 ||
%                          opcode == BytecodeInstructionSafe::Opcode::INITOBJ_RANGE_V8_ID16) &&
%                         cls != nullptr && cls->IsArrayClass())) {
%                isArrayConstructorCall = true;
%                Type tp = Type {cls};
%                AddType(inst.GetOffset(), &tp);
%            } else {
%                calledMethod = classLinker_->GetMethod(*method_, methodId, &errorHandler);
%            }
%        }
%        if (calledMethod != nullptr) {
%            AddMethod(inst.GetOffset(), calledMethod);
%            auto classType = Type {calledMethod->GetClass()};
%            AddType(inst.GetOffset(), &classType);
%        } else if (!isArrayConstructorCall) {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot resolve method with id " << id << " in method "
%                                 << method_->GetFullName();
%        }
%    }),
%    "StaticFieldId_" => %({
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        auto fieldIdx = method_->GetClass()->ResolveFieldIndex(id.AsIndex());
%        auto *cachedField = classLinker_->GetField(*method_, fieldIdx, true, &errorHandler);
%        if (cachedField != nullptr) {
%            AddField(inst.GetOffset(), cachedField);
%            auto classType = Type {cachedField->GetClass()};
%            AddType(inst.GetOffset(), &classType);
%        } else {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot resolve field with id " << id << " in method "
%                                 << method_->GetFullName();
%        }
%    }),
%    "FieldId_" => %({
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        auto fieldIdx = method_->GetClass()->ResolveFieldIndex(id.AsIndex());
%        auto *cachedField = classLinker_->GetField(*method_, fieldIdx, false, &errorHandler);
%        if (cachedField != nullptr) {
%            AddField(inst.GetOffset(), cachedField);
%            auto classType = Type {cachedField->GetClass()};
%            AddType(inst.GetOffset(), &classType);
%        } else {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot resolve field with id " << id << " in method "
%                                 << method_->GetFullName();
%        }
%    }),
%    "TypeId_" => %({
%        auto classIdx = method_->GetClass()->ResolveClassIndex(id.AsIndex());
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        auto *cachedClass = classLinker_->GetClass(*method_, classIdx, &errorHandler);
%        if (cachedClass != nullptr) {
%            auto classType = Type {cachedClass};
%            AddType(inst.GetOffset(), &classType);
%        } else {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot resolve class with id " << id << " in method "
%                                 << method_->GetFullName();
%        }
%    }),
%    "StringId_" => %({
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        auto cachedStringClass = classLinker_->GetClass(langContext_.GetStringClassDescriptor(), false, classLinkerCtx_, &errorHandler);
%        if (cachedStringClass != nullptr) {
%            auto cachedStringType = Type {cachedStringClass};
%            AddType(inst.GetOffset(), &cachedStringType);
%        } else {
%            LOG(DEBUG, VERIFIER) << "JOBFILL: Cannot resolve string class in method " << method_->GetFullName();
%        }
%    }),
%    "ClassId_" => %({
%        ScopedChangeThreadStatus st(ManagedThread::GetCurrent(), ThreadStatus::RUNNING);
%        // We are in lda.type bytecode handler. acc type is going to be java.lang.Class
%        auto cachedClassClass = classLinker_->GetClass(langContext_.GetClassClassDescriptor(), false, classLinkerCtx_, &errorHandler);
%        if (cachedClassClass != nullptr) {
%            auto cachedClassType = Type {cachedClassClass};
%            AddType(inst.GetOffset(), &cachedClassType);
%        } else {
%            LOG(ERROR, VERIFIER) << "JOBFILL: Cannot resolve Class class in method " << method_->GetFullName();
%        }
%    }),
%    "GetNext_" => %(
%        if (inst.IsLast()) {
%            return true;
%        }
%
%        auto nextInst = inst.GetNext();
%        if (!inst.IsPrimaryOpcodeValid()) {
%            LOG(DEBUG, VERIFIER) << "Opcode value is out of range. "
%                                 << "Current value is: " << static_cast<int>(inst.GetPrimaryOpcode())
%                                 << ". Allowed value is in the interval: [0, ) + dt_non_prefixed_min_minus + %(] U "
%                                 << "[) + dt_non_prefixed_max_plus + %(, ) + dt_prefixed_min_plus + %(] U "
%                                 << "[) + dt_prefixed_max_plus + %(, 255]";
%            return false;
%        }
%        if (!nextInst.IsValid()) {
%            LOG(DEBUG, VERIFIER) << "Invalid instruction. "
%                                 << "Offset of last valid instruction: " << inst.GetOffset() << ". "
%                                 << "Last valid instrution: " << inst;
%            return false;
%        }
%        inst = nextInst;
%    }
%    goto* dispatchTable[inst.GetPrimaryOpcode()];
%    )
%}
%
% full_code_hash = Hash.new("")
% dispatch_table_hash.each { |key_comb, value_comb|
%    body_gen_parts.each { |key_parts, value_parts|
%        string_to_compare_with = key_comb.to_s
%        flag_compare = string_to_compare_with.include?(key_parts.to_s)
%        if flag_compare == true
%            full_code_hash[key_comb] += value_parts
%        end
%    }
% }
%
% full_code_hash.each { |key_full, code|
%   dispatch_table_hash[key_full].each { |label|
<%= label%>:;
%   }
    {
<%= code %>
% }
%
HANDLE_INVALID:
    LOG(ERROR, VERIFIER) << "Incorrect opcode";
    return false;

% Panda::prefixes.each do |p|
% dt_sec_opcode_bound = Panda::dispatch_table.secondary_opcode_bound(p)
% dt_sec_opcode_offset = Panda::dispatch_table.secondary_opcode_offset(p)
HANDLE_<%= p.handler_name %>:
    secondaryOpcode = inst.GetSecondaryOpcode();
    LOG(DEBUG, VERIFIER) << "Prefix subdispatch: " << "<%= p.name %>, " << static_cast<int32_t>(secondaryOpcode);

    // NOLINTNEXTLINE(readability-magic-numbers)
    if (secondaryOpcode > <%= dt_sec_opcode_bound %> ) {
        LOG(ERROR, VERIFIER) << "Incorrect opcode";
        return false;
    }

    // NOLINTNEXTLINE(readability-magic-numbers, cppcoreguidelines-avoid-goto)
    goto *dispatchTable[<%= dt_sec_opcode_offset %> + secondaryOpcode];

% end

#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
}

}  // namespace ark::verifier
